<?php

# -- DÉBUT NOTE DE LICENCE -------------------------------------
#
# Ce fichier fait partie de Clicpage.
#
# Copyright (c) 2010 Francois (www.clicpage.net)
# Distribué selon les termes de la licence GPL version 2.0.
# Consultez le fichier LICENCE ou
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# Ce fichier a été créé le 1 septembre 2010 et est inspiré
# de Clearbricks.
#
# Note de licence de Clearbricks:
# Copyright (c) 2003-2010 Olivier Meunier & Association Dotclear
# Clearbricks est distribué selon les termes de la licence GPL
# version 2.0.
#
# -- FIN NOTE DE LICENCE ---------------------------------------

class cpTemplate extends template
{
	private $core;
	private $current_tag;
	
	protected $unknown_value_handler = null;
	protected $unknown_block_handler = null;
	
	function __construct($cache_dir,$self_name,$core)
	{
		parent::__construct($cache_dir,$self_name);
		
		$this->core =& $core;
		
		$this->remove_php = true;		# TODO: option dans les réglages
		$this->use_cache = true;
		
		$this->tag_block = '<tpl:(\w+)(?:(\s+.*?)>|>)((?:[^<]|<(?!/?tpl:\1)|(?R))*)</tpl:\1>';
		$this->tag_value = '{{tpl:(\w+)(\s(.*?))?}}';
	}
	
	public function getData($________)
	{
		# --COMPORTEMENT-- tplAvantDonnees
		if ($this->core->comportementExiste('tplAvantDonnees'))
		{
			self::$_r = $this->core->executerComportement('tplAvantDonnees',$this->core);
			if (self::$_r) {
				return self::$_r;
			}
		}
		
		parent::getData($________);
		
		# --COMPORTEMENT-- tplApresDonnees
		if ($this->core->comportementExiste('tplApresDonnees')) {
			$this->core->executerComportement('tplApresDonnees',$this->core,self::$_r);
		}
		
		return self::$_r;
	}
	
	protected function compileFile($file)
	{
		$fc = file_get_contents($file);
		
		$this->compile_stack[] = $file;
		
		# Remove every PHP tags
		if ($this->remove_php)
		{
			$fc = preg_replace('/<\?(?=php|=|\s).*?\?>/ms','',$fc);
		}
		
		# Transform what could be considered as PHP short tags
		$fc = preg_replace('/(<\?(?!php|=|\s))(.*?)(\?>)/ms',
		'<?php echo "$1"; ?>$2<?php echo "$3"; ?>',$fc);
		
		# Remove template comments <!-- #... -->
		$fc = preg_replace('/(^\s*)?<!-- #(.*?)-->/ms','',$fc);
		
		# Lexer part : split file into small pieces
		# each array entry will be either a tag or plain text
		$blocks = preg_split(
			'#(<tpl:\w+[^>]*>)|(</tpl:\w+>)|({{tpl:\w+[^}]*}})#msu',$fc,-1,
			PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
		
		# Next : build semantic tree from tokens.
		$rootNode = new tplNode();
		$node = $rootNode;
		$errors = array();
		foreach ($blocks as $id => $block) {
			$isblock = preg_match('#<tpl:(\w+)(?:(\s+.*?)>|>)|</tpl:(\w+)>|{{tpl:(\w+)(\s(.*?))?}}#ms',$block,$match);
			if ($isblock == 1) {
				if (substr($match[0],1,1) == '/') {
					// Closing tag, check if it matches current opened node
					$tag = $match[3];
					if (($node instanceof tplNodeBlock) && $node->getTag() == $tag) {
						$node->setClosing();
						$node = $node->getParent();
					} else {
						// Closing tag does not match opening tag
						// Search if it closes a parent tag
						$search = $node;
						while($search->getTag() != 'ROOT' && $search->getTag() != $tag) {
							$search = $search->getParent();
						}
						if ($search->getTag() == $tag) {
							$errors[] = sprintf(
								__('Did not find closing tag for block <tpl:%s>. Content has been ignored.'),
								html::escapeHTML($node->getTag()));
							$search->setClosing();
							$node = $search->getParent();
						} else {
							$errors[]=sprintf(
								__('Unexpected closing tag </tpl:%s> found.'),
								$tag);;
						}
					}
				} elseif (substr($match[0],0,1) == '{') {
					// Value tag
					$tag = $match[4];
					$str_attr = '';
					$attr = array();
					if (isset($match[6])) {
						$str_attr = $match[6];
						$attr = $this->getAttrs($match[6]);
					}
					$node->addChild(new tplNodeValue($tag,$attr,$str_attr));
				} else {
					// Opening tag, create new node and dive into it
					$tag = $match[1];
					$newnode = new tplNodeBlock($tag,isset($match[2])?$this->getAttrs($match[2]):array());
					$node->addChild($newnode);
					$node = $newnode;
				}
			} else {
				// Simple text
				$node->addChild(new tplNodeText($block));
			}
		}
		
		$err = "";
		if (count($errors) > 0) {
			$err = "\n\n<!-- \n".
				__('WARNING: the following errors have been found while parsing template file :').
				"\n * ".
				join("\n * ",$errors).
				"\n -->\n";
		}
		
		return $rootNode->compile($this).$err;
	}
    
	public function compileBlockNode($tag,$attr,$content)
	{
		$this->current_tag = $tag;
		$attr = new ArrayObject($attr);
		# --COMPORTEMENT-- tplAvantBloc
		$res = $this->core->executerComportement('tplAvantBloc',$this->core,$this->current_tag,$attr);
		
		if (isset($this->blocks[$this->current_tag])) {
			$res .= call_user_func($this->blocks[$this->current_tag],$attr,$content);
		} elseif ($this->unknown_block_handler != null) {
			$res .= call_user_func($this->unknown_block_handler,$this->current_tag,$attr,$content);
		}
		
		# --COMPORTEMENT-- tplApresBloc
		$res .= $this->core->executerComportement('tplApresBloc',$this->core,$this->current_tag,$attr);
		
		return $res;
	}
	
	public function compileValueNode($tag,$attr,$str_attr)
	{
		$this->current_tag = $tag;
		
		$attr = new ArrayObject($attr);
		# --COMPORTEMENT-- tplAvantValeur
		$res = $this->core->executerComportement('tplAvantValeur',$this->core,$this->current_tag,$attr);
		
		if (isset($this->values[$this->current_tag])) {
			$res .= call_user_func($this->values[$this->current_tag],$attr,ltrim($str_attr));
		} elseif ($this->unknown_value_handler != null) {
			$res .= call_user_func($this->unknown_value_handler,$this->current_tag,$attr,$str_attr);
		}
        
		# --COMPORTEMENT-- tplApresValeur
		$res .= $this->core->executerComportement('tplApresValeur',$this->core,$this->current_tag,$attr);
		
		return $res;
	}
    
	public function setUnknownValueHandler($callback)
	{
		if (is_callable($callback)) {
			$this->unknown_value_handler = $callback;
		}
	}
    
	public function setUnknownBlockHandler($callback)
	{
		if (is_callable($callback)) {
			$this->unknown_block_handler = $callback;
		}
	}

}

# Template nodes, for parsing purposes

# Generic list node, this one may only be instanciated 
# once for root element
class tplNode 
{
	# Basic tree structure : links to parent, children forrest
	protected $parentNode;
	protected $children;
	
	public function __construct() {
		$this->children = array();
		$this->parentNode = null;
	}
	
	// Returns compiled block
	public function compile($tpl) {
		$res='';
		foreach ($this->children as $child) {
			$res .= $child->compile($tpl);
		}
		return $res;
	}
	
	# Add a children to current node
	public function addChild ($child) {
		$this->children[] = $child;
		$child->setParent($this);
	}
	
	# Defines parent for current node
	protected function setParent($parent) {
		$this->parentNode = $parent;
	}
	
	# Retrieves current node parent.
	# If parent is root node, null is returned
	public function getParent() {
		return $this->parentNode;
	}
	
	# Current node tag
	public function getTag() {
		return "ROOT";
	}
}

// Text node, for any non-tpl content
class tplNodeText extends tplNode 
{
	// Simple text node, only holds its content
	protected $content;
	
	public function __construct($text) {
		parent::__construct();
		$this->content=$text;
	}
	
	public function compile($tpl) {
		return $this->content;
	}
	
	public function getTag() {
		return "TEXT";
	}
}

// Block node, for all <tpl:Tag>...</tpl:Tag>
class tplNodeBlock extends tplNode 
{
	protected $attr;
	protected $tag;
	protected $closed;
    
	public function __construct($tag,$attr) {
		parent::__construct();
		$this->content='';
		$this->tag = $tag;
		$this->attr = $attr;
		$this->closed=false;
	}
	public function setClosing() {
		$this->closed = true;
	}
	public function compile($tpl) {
		if ($this->closed) {
			$content = parent::compile($tpl);
			return $tpl->compileBlockNode($this->tag,$this->attr,$content);
		} else {
			// if tag has not been closed, silently ignore its content...
			return '';
		}
	}
	public function getTag() {
		return $this->tag;
	}
}

// Value node, for all {{tpl:Tag}}
class tplNodeValue extends tplNode 
{
	protected $attr;
	protected $str_attr;
	protected $tag;
	
	public function __construct($tag,$attr,$str_attr) {
		parent::__construct();
		$this->content='';
		$this->tag = $tag;
		$this->attr = $attr;
		$this->str_attr = $str_attr;
	}
	
	public function compile($tpl) {
		return $tpl->compileValueNode($this->tag,$this->attr,$this->str_attr);
	}
	
	public function getTag() {
		return $this->tag;
	}
}

?>